/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED 'AS IS' WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.
   Author: Leon Finker  1/2001
**************************************************************************/
// IDataObjectImpl.cpp: implementation of the CIDataObjectImpl class.
//////////////////////////////////////////////////////////////////////
#pragma warning(push)
#pragma warning(disable: 4091)
#include <shlobj.h>
#pragma warning(pop)
#include "DragDropImpl.h"
#include "ExplorerResource.h"

//////////////////////////////////////////////////////////////////////
// CIDataObject Class
//////////////////////////////////////////////////////////////////////

CIDataObject::CIDataObject(CIDropSource* pDropSource): 
m_cRefCount(0), m_pDropSource(pDropSource)
{
}

CIDataObject::~CIDataObject()
{
	for(size_t i = 0; i < m_StgMedium.size(); ++i)
		ReleaseStgMedium(m_StgMedium[i]);
	m_StgMedium.clear();
	m_ArrFormatEtc.clear();
}

STDMETHODIMP CIDataObject::QueryInterface(/* [in] */ REFIID riid,
/* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
{
	*ppvObject = NULL;
	if (IID_IUnknown==riid || IID_IDataObject==riid)
             *ppvObject=this;
   	/*if(riid == IID_IAsyncOperation)
		*ppvObject=(IAsyncOperation*)this;*/
    if (NULL!=*ppvObject)
    {
        ((LPUNKNOWN)*ppvObject)->AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CIDataObject::AddRef( void)
{
	// ATLTRACE("CIDataObject::AddRef\n");
	return ++m_cRefCount;
}

STDMETHODIMP_(ULONG) CIDataObject::Release( void)
{
   // ATLTRACE("CIDataObject::Release\n");
   long nTemp;
   nTemp = --m_cRefCount;
   return nTemp;
}

STDMETHODIMP CIDataObject::GetData( 
    /* [unique][in] */ FORMATETC __RPC_FAR *pformatetcIn,
    /* [out] */ STGMEDIUM __RPC_FAR *pmedium)
{ 
	// ATLTRACE("CIDataObject::GetData\n");
	if(pformatetcIn == NULL || pmedium == NULL)
		return E_INVALIDARG;
	pmedium->hGlobal = NULL;

	// ATLASSERT(m_StgMedium.size() == m_ArrFormatEtc.size());
	for(size_t i=0; i < m_ArrFormatEtc.size(); ++i)
	{
		if(pformatetcIn->tymed & m_ArrFormatEtc[i]->tymed &&
		   pformatetcIn->dwAspect == m_ArrFormatEtc[i]->dwAspect &&
		   pformatetcIn->cfFormat == m_ArrFormatEtc[i]->cfFormat)
		{
			CopyMedium(pmedium, m_StgMedium[i], m_ArrFormatEtc[i]);
			return S_OK;
		}
	}
	return DV_E_FORMATETC;
}

STDMETHODIMP CIDataObject::GetDataHere( 
    /* [unique][in] */ FORMATETC __RPC_FAR *pformatetc,
    /* [out][in] */ STGMEDIUM __RPC_FAR *pmedium)
{ 
   // ATLTRACE("CIDataObject::GetDataHere\n");
   
   return E_NOTIMPL;
}

STDMETHODIMP CIDataObject::QueryGetData( 
   /* [unique][in] */ FORMATETC __RPC_FAR *pformatetc)
{ 
	// ATLTRACE("CIDataObject::QueryGetData\n");
	if(pformatetc == NULL)
		return E_INVALIDARG;

	//support others if needed DVASPECT_THUMBNAIL  //DVASPECT_ICON   //DVASPECT_DOCPRINT
	if (!(DVASPECT_CONTENT & pformatetc->dwAspect))
		return (DV_E_DVASPECT);
	HRESULT  hr = DV_E_TYMED;
	for(size_t i = 0; i < m_ArrFormatEtc.size(); ++i)
	{
	   if(pformatetc->tymed & m_ArrFormatEtc[i]->tymed)
	   {
		  if(pformatetc->cfFormat == m_ArrFormatEtc[i]->cfFormat)
			 return S_OK;
		  else
			 hr = DV_E_CLIPFORMAT;
	   }
	   else
		  hr = DV_E_TYMED;
	}
	return hr;
}

STDMETHODIMP CIDataObject::GetCanonicalFormatEtc( 
    /* [unique][in] */ FORMATETC __RPC_FAR *pformatectIn,
    /* [out] */ FORMATETC __RPC_FAR *pformatetcOut)
{ 
	// ATLTRACE("CIDataObject::GetCanonicalFormatEtc\n");
	if (pformatetcOut == NULL)
		return E_INVALIDARG;
	return DATA_S_SAMEFORMATETC;
}

STDMETHODIMP CIDataObject::SetData( 
    /* [unique][in] */ FORMATETC __RPC_FAR *pformatetc,
    /* [unique][in] */ STGMEDIUM __RPC_FAR *pmedium,
    /* [in] */ BOOL fRelease)
{ 
	// ATLTRACE("CIDataObject::SetData\n");
	if(pformatetc == NULL || pmedium == NULL)
      return E_INVALIDARG;

	// ATLASSERT(pformatetc->tymed == pmedium->tymed);
	FORMATETC* fetc=new FORMATETC;
	STGMEDIUM* pStgMed = new STGMEDIUM;

	if(fetc == NULL || pStgMed == NULL)
		return E_OUTOFMEMORY;

	ZeroMemory(fetc,sizeof(FORMATETC));
	ZeroMemory(pStgMed,sizeof(STGMEDIUM));

	*fetc = *pformatetc;
	m_ArrFormatEtc.push_back(fetc);

    if(fRelease)
      *pStgMed = *pmedium;
    else
    {
		CopyMedium(pStgMed, pmedium, pformatetc);
	}
	m_StgMedium.push_back(pStgMed);

    return S_OK;
}
void CIDataObject::CopyMedium(STGMEDIUM* pMedDest, STGMEDIUM* pMedSrc, FORMATETC* pFmtSrc)
{
		switch(pMedSrc->tymed)
		{
		case TYMED_HGLOBAL:
			pMedDest->hGlobal = (HGLOBAL)OleDuplicateData(pMedSrc->hGlobal,pFmtSrc->cfFormat, NULL);
			break;
		case TYMED_GDI:
			pMedDest->hBitmap = (HBITMAP)OleDuplicateData(pMedSrc->hBitmap,pFmtSrc->cfFormat, NULL);
			break;
		case TYMED_MFPICT:
			pMedDest->hMetaFilePict = (HMETAFILEPICT)OleDuplicateData(pMedSrc->hMetaFilePict,pFmtSrc->cfFormat, NULL);
			break;
		case TYMED_ENHMF:
			pMedDest->hEnhMetaFile = (HENHMETAFILE)OleDuplicateData(pMedSrc->hEnhMetaFile,pFmtSrc->cfFormat, NULL);
			break;
		case TYMED_FILE:
			pMedSrc->lpszFileName = (LPOLESTR)OleDuplicateData(pMedSrc->lpszFileName,pFmtSrc->cfFormat, NULL);
			break;
		case TYMED_ISTREAM:
			pMedDest->pstm = pMedSrc->pstm;
			pMedSrc->pstm->AddRef();
			break;
		case TYMED_ISTORAGE:
			pMedDest->pstg = pMedSrc->pstg;
			pMedSrc->pstg->AddRef();
			break;
		case TYMED_NULL:
		default:
			break;
		}
		pMedDest->tymed = pMedSrc->tymed;
		pMedDest->pUnkForRelease = NULL;
		if(pMedSrc->pUnkForRelease != NULL)
		{
			pMedDest->pUnkForRelease = pMedSrc->pUnkForRelease;
			pMedSrc->pUnkForRelease->AddRef();
		}
}
STDMETHODIMP CIDataObject::EnumFormatEtc(
   /* [in] */ DWORD dwDirection,
   /* [out] */ IEnumFORMATETC __RPC_FAR *__RPC_FAR *ppenumFormatEtc)
{ 
	// ATLTRACE("CIDataObject::EnumFormatEtc\n");
	if(ppenumFormatEtc == NULL)
      return E_POINTER;

	*ppenumFormatEtc=NULL;
	switch (dwDirection)
    {
      case DATADIR_GET:
         *ppenumFormatEtc= new CEnumFormatEtc(m_ArrFormatEtc);
		 if(*ppenumFormatEtc == NULL)
			 return E_OUTOFMEMORY;
         (*ppenumFormatEtc)->AddRef(); 
         break;
      
	  case DATADIR_SET:
      default:
		 return E_NOTIMPL;
         break;
    }

   return S_OK;
}

STDMETHODIMP CIDataObject::DAdvise( 
   /* [in] */ FORMATETC __RPC_FAR *pformatetc,
   /* [in] */ DWORD advf,
   /* [unique][in] */ IAdviseSink __RPC_FAR *pAdvSink,
   /* [out] */ DWORD __RPC_FAR *pdwConnection)
{ 
	// ATLTRACE("CIDataObject::DAdvise\n");
	return OLE_E_ADVISENOTSUPPORTED;
}

STDMETHODIMP CIDataObject::DUnadvise( 
   /* [in] */ DWORD dwConnection)
{
	// ATLTRACE("CIDataObject::DUnadvise\n");
	return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE CIDataObject::EnumDAdvise( 
   /* [out] */ IEnumSTATDATA __RPC_FAR *__RPC_FAR *ppenumAdvise)
{
	// ATLTRACE("CIDataObject::EnumDAdvise\n");
	return OLE_E_ADVISENOTSUPPORTED;
}

//////////////////////////////////////////////////////////////////////
// CIDropSource Class
//////////////////////////////////////////////////////////////////////

STDMETHODIMP CIDropSource::QueryInterface(/* [in] */ REFIID riid,
										 /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
{
   *ppvObject = NULL;
   if (IID_IUnknown==riid || IID_IDropSource==riid)
       *ppvObject=this;

    if (*ppvObject != NULL)
    {
       ((LPUNKNOWN)*ppvObject)->AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CIDropSource::AddRef( void)
{
	// ATLTRACE("CIDropSource::AddRef\n");
	return ++m_cRefCount;
}

STDMETHODIMP_(ULONG) CIDropSource::Release( void)
{
	// ATLTRACE("CIDropSource::Release\n");
   long nTemp;
   nTemp = --m_cRefCount;
   // ATLASSERT(nTemp >= 0);
   if(nTemp==0)
      delete this;
   return nTemp;
}

STDMETHODIMP CIDropSource::QueryContinueDrag( 
    /* [in] */ BOOL fEscapePressed,
    /* [in] */ DWORD grfKeyState)
{
   //// ATLTRACE("CIDropSource::QueryContinueDrag\n");
   if(fEscapePressed)
      return DRAGDROP_S_CANCEL;
   if(!(grfKeyState & (MK_LBUTTON|MK_RBUTTON)))
   {
	  m_bDropped = true;
      return DRAGDROP_S_DROP;
   }

   return S_OK;

}

STDMETHODIMP CIDropSource::GiveFeedback(
    /* [in] */ DWORD dwEffect)
{
	//// ATLTRACE("CIDropSource::GiveFeedback\n");
	return DRAGDROP_S_USEDEFAULTCURSORS;
}

//////////////////////////////////////////////////////////////////////
// CEnumFormatEtc Class
//////////////////////////////////////////////////////////////////////

CEnumFormatEtc::CEnumFormatEtc(const vector<FORMATETC>& ArrFE):
m_cRefCount(0),m_iCur(0)
{
   // ATLTRACE("CEnumFormatEtc::CEnumFormatEtc()\n");
   for(size_t i = 0; i < ArrFE.size(); ++i)
		m_pFmtEtc.push_back(ArrFE[i]);
}

CEnumFormatEtc::CEnumFormatEtc(const vector<FORMATETC*>& ArrFE):
m_cRefCount(0),m_iCur(0)
{
   for(size_t i = 0; i < ArrFE.size(); ++i)
		m_pFmtEtc.push_back(*ArrFE[i]);
}

STDMETHODIMP  CEnumFormatEtc::QueryInterface(REFIID refiid, void FAR* FAR* ppv)
{
   // ATLTRACE("CEnumFormatEtc::QueryInterface()\n");
   *ppv = NULL;
   if (IID_IUnknown==refiid || IID_IEnumFORMATETC==refiid)
             *ppv=this;

    if (*ppv != NULL)
    {
        ((LPUNKNOWN)*ppv)->AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CEnumFormatEtc::AddRef(void)
{
   // ATLTRACE("CEnumFormatEtc::AddRef()\n");
   return ++m_cRefCount;
}

STDMETHODIMP_(ULONG) CEnumFormatEtc::Release(void)
{
   // ATLTRACE("CEnumFormatEtc::Release()\n");
   long nTemp = --m_cRefCount;
   // ATLASSERT(nTemp >= 0);
   if(nTemp == 0)
     delete this;

   return nTemp; 
}

STDMETHODIMP CEnumFormatEtc::Next( ULONG celt,LPFORMATETC lpFormatEtc, ULONG FAR *pceltFetched)
{
   // ATLTRACE("CEnumFormatEtc::Next()\n");
   if(pceltFetched != NULL)
   	   *pceltFetched=0;
	
   ULONG cReturn = celt;

   if(celt <= 0 || lpFormatEtc == NULL || m_iCur >= m_pFmtEtc.size())
      return S_FALSE;

   if(pceltFetched == NULL && celt != 1) // pceltFetched can be NULL only for 1 item request
      return S_FALSE;

	while (m_iCur < m_pFmtEtc.size() && cReturn > 0)
	{
		*lpFormatEtc++ = m_pFmtEtc[m_iCur++];
		--cReturn;
	}
	if (pceltFetched != NULL)
		*pceltFetched = celt - cReturn;

    return (cReturn == 0) ? S_OK : S_FALSE;
}
   
STDMETHODIMP CEnumFormatEtc::Skip(ULONG celt)
{
	// ATLTRACE("CEnumFormatEtc::Skip()\n");
	if((m_iCur + int(celt)) >= m_pFmtEtc.size())
		return S_FALSE;
	m_iCur += celt;
	return S_OK;
}

STDMETHODIMP CEnumFormatEtc::Reset(void)
{
   // ATLTRACE("CEnumFormatEtc::Reset()\n");
   m_iCur = 0;
   return S_OK;
}
               
STDMETHODIMP CEnumFormatEtc::Clone(IEnumFORMATETC FAR * FAR*ppCloneEnumFormatEtc)
{
  // ATLTRACE("CEnumFormatEtc::Clone()\n");
  if(ppCloneEnumFormatEtc == NULL)
      return E_POINTER;
      
  CEnumFormatEtc *newEnum = new CEnumFormatEtc(m_pFmtEtc);
  if(newEnum ==NULL)
		return E_OUTOFMEMORY;  	
  newEnum->AddRef();
  newEnum->m_iCur = m_iCur;
  *ppCloneEnumFormatEtc = newEnum;
  return S_OK;
}

//////////////////////////////////////////////////////////////////////
// CIDropTarget Class
//////////////////////////////////////////////////////////////////////
CIDropTarget::CIDropTarget(void): 
	m_hTargetWnd(NULL),
	m_cRefCount(0), m_bAllowDrop(false),
	m_pDropTargetHelper(NULL), m_pSupportedFrmt(NULL)
{ 
	// ATLASSERT(m_hTargetWnd != NULL);

	if(FAILED(CoCreateInstance(CLSID_DragDropHelper,NULL,CLSCTX_INPROC_SERVER,
                     IID_IDropTargetHelper,(LPVOID*)&m_pDropTargetHelper)))
		m_pDropTargetHelper = NULL;
}

CIDropTarget::~CIDropTarget()
{
	if(m_pDropTargetHelper != NULL)
	{
		m_pDropTargetHelper->Release();
		m_pDropTargetHelper = NULL;
	}
}

HRESULT STDMETHODCALLTYPE CIDropTarget::QueryInterface( /* [in] */ REFIID riid,
						/* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
{
   *ppvObject = NULL;
   if (IID_IUnknown==riid || IID_IDropTarget==riid)
			 *ppvObject=this;

	if (*ppvObject != NULL)
	{
		((LPUNKNOWN)*ppvObject)->AddRef();
		return S_OK;
	}
	return E_NOINTERFACE;
}

ULONG STDMETHODCALLTYPE CIDropTarget::Release( void)
{
   // ATLTRACE("CIDropTarget::Release\n");
   long nTemp;
   nTemp = --m_cRefCount;
   // ATLASSERT(nTemp >= 0);
   if(nTemp==0)
	  delete this;
   return nTemp;
}

bool CIDropTarget::QueryDrop(DWORD grfKeyState, LPDWORD pdwEffect)
{  
	DWORD dwOKEffects = *pdwEffect; 

	if(!m_bAllowDrop)
	{
		*pdwEffect = DROPEFFECT_NONE;
		return false;
	}
	// CTRL+SHIFT  -- DROPEFFECT_LINK
	// CTRL        -- DROPEFFECT_COPY
	// SHIFT       -- DROPEFFECT_MOVE
	// no modifier -- DROPEFFECT_MOVE or whatever is allowed by src
 	*pdwEffect = (grfKeyState & MK_CONTROL) ?
				 ( (grfKeyState & MK_SHIFT) ? DROPEFFECT_LINK : DROPEFFECT_COPY ):
				 ( (grfKeyState & MK_SHIFT) ? DROPEFFECT_MOVE : 0 );
	if(*pdwEffect == 0) 
	{
		// No modifier keys used by user while dragging. 
		if (DROPEFFECT_COPY & dwOKEffects) {
			*pdwEffect = DROPEFFECT_COPY;
		} else if (DROPEFFECT_MOVE & dwOKEffects) {
			*pdwEffect = DROPEFFECT_MOVE;
		} else if (DROPEFFECT_LINK & dwOKEffects) {
			*pdwEffect = DROPEFFECT_LINK;
		} else {
			*pdwEffect = DROPEFFECT_NONE;
		}
	} 
	else
	{
	   // Check if the drag source application allows the drop effect desired by user.
	   // The drag source specifies this in DoDragDrop
		if(!(*pdwEffect & dwOKEffects)) {
			*pdwEffect = DROPEFFECT_NONE;
		}
	}

	::SendMessage(m_hTargetWnd, EXM_QUERYDROP, grfKeyState, *pdwEffect);

	return (DROPEFFECT_NONE == *pdwEffect) ? false : true;
}

HRESULT STDMETHODCALLTYPE CIDropTarget::DragEnter(
    /* [unique][in] */ IDataObject __RPC_FAR *pDataObj,
    /* [in] */ DWORD grfKeyState,
    /* [in] */ POINTL pt,
    /* [out][in] */ DWORD __RPC_FAR *pdwEffect)
{
	// ATLTRACE("CIDropTarget::DragEnter\n");
	if(pDataObj == NULL)
		return E_INVALIDARG;

	if(m_pDropTargetHelper)
		m_pDropTargetHelper->DragEnter(m_hTargetWnd, pDataObj, (LPPOINT)&pt, *pdwEffect);
	//IEnumFORMATETC* pEnum;
	//pDataObj->EnumFormatEtc(DATADIR_GET,&pEnum);
	//FORMATETC ftm;
	//for()
	//pEnum->Next(1,&ftm,0);
	//pEnum->Release();
	m_pSupportedFrmt = NULL;
	for(size_t i =0; i<m_formatetc.size(); ++i)
	{
		m_bAllowDrop = (pDataObj->QueryGetData(&m_formatetc[i]) == S_OK)?true:false;
		if(m_bAllowDrop)
		{
			m_pSupportedFrmt = &m_formatetc[i];
			break;
		}
	}

	QueryDrop(grfKeyState, pdwEffect);
	return S_OK;
}

HRESULT STDMETHODCALLTYPE CIDropTarget::DragOver( 
        /* [in] */ DWORD grfKeyState,
        /* [in] */ POINTL pt,
        /* [out][in] */ DWORD __RPC_FAR *pdwEffect)
{
	// ATLTRACE("CIDropTarget::DragOver\n");
	if(m_pDropTargetHelper)
		m_pDropTargetHelper->DragOver((LPPOINT)&pt, *pdwEffect);
	QueryDrop(grfKeyState, pdwEffect);
	return S_OK;
}

HRESULT STDMETHODCALLTYPE CIDropTarget::DragLeave( void)
{
	// ATLTRACE("CIDropTarget::DragLeave\n");

	if(m_pDropTargetHelper)
		m_pDropTargetHelper->DragLeave();

	::SendMessage(m_hTargetWnd, EXM_DRAGLEAVE, 0, 0);
	
	m_bAllowDrop = false;
	m_pSupportedFrmt = NULL;
	return S_OK;
}

HRESULT STDMETHODCALLTYPE CIDropTarget::Drop(
	/* [unique][in] */ IDataObject __RPC_FAR *pDataObj,
    /* [in] */ DWORD grfKeyState, /* [in] */ POINTL pt, 
	/* [out][in] */ DWORD __RPC_FAR *pdwEffect)
{
	// ATLTRACE("CIDropTarget::Drop\n");
	if (pDataObj == NULL)
		return E_INVALIDARG;	

	if(m_pDropTargetHelper)
		m_pDropTargetHelper->Drop(pDataObj, (LPPOINT)&pt, *pdwEffect);

	if(QueryDrop(grfKeyState, pdwEffect))
	{
		if(m_bAllowDrop && m_pSupportedFrmt != NULL)
		{
			STGMEDIUM medium;
			if(pDataObj->GetData(m_pSupportedFrmt, &medium) == S_OK)
			{
				if(OnDrop(m_pSupportedFrmt, medium, pdwEffect)) //does derive class wants us to free medium?
					ReleaseStgMedium(&medium);
			}
		}
	}
	m_bAllowDrop=false;
	*pdwEffect = DROPEFFECT_NONE;
	m_pSupportedFrmt = NULL;
	return S_OK;
}

//////////////////////////////////////////////////////////////////////
// CIDragSourceHelper Class
//////////////////////////////////////////////////////////////////////
